﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using VsSpeakerCompanion;
using System.ComponentModel.Composition;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Classification;
using TwitterAPI;
using System.Windows.Threading;
using System.Windows.Input;
using System.Windows.Media.Animation;
using System.Windows;
using System.Windows.Media;
using System.Windows.Controls;
using VsSpeakerCompanion.UI;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio;

namespace FontSizeWidget
{
    /// <summary>
    /// Allows the audience to send feedback about the currently used font size
    /// (e.g. the usual "I cant read the screen from the back, font is too small!")
    /// </summary>
    public class FontSizeWidget : IWidget
    {
        // couple of lists to keep track of received feedback
        private List<string> bigVoters;
        private List<string> smallVoters;

        // our UI
        FontSize fontSize;

        // we will use this service to animate our widget when trying to get the speaker's attention
        IWidgetAnimationService widgetAnimationService;

        // we need this service to access some core VS functionality
        IVisualStudioService visualStudioService;

        // and we also need a service provider
        IServiceProvider serviceProvider;

        private IEditorFormatMapService editorFormatMapService;

        public FontSizeWidget(IVisualStudioService visualStudioService, IEditorFormatMapService editorFormatMapService, TwitterService twitterService, IWidgetAnimationService widgetAnimationService, IServiceProvider serviceProvider)
        {
            this.visualStudioService = visualStudioService;
            this.serviceProvider = serviceProvider;
            this.editorFormatMapService = editorFormatMapService;
            this.widgetAnimationService = widgetAnimationService;
            fontSize = new FontSize();

            // listen to clicks on our widget (this means the speaker ackowledged the received feedback)
            fontSize.MouseDown +=new MouseButtonEventHandler(fontSize_MouseDown);
            // listen to clicks on the big "A" for increasing the current font size
            fontSize.BigAClicked += new EventHandler(fontSize_BigAClicked);
            // listen to clicks on the small "A" for decreasing the current font size
            fontSize.SmallAClicked += new EventHandler(fontSize_SmallAClicked);

            bigVoters = new List<string>();
            smallVoters = new List<string>();

            // register the two feedback messages this widget understands
            twitterService.RegisterCommand("font too small", ProcessSmallMessages);
            twitterService.RegisterCommand("font too big", ProcessBigMessages);
        }

        /// <summary>
        /// Keep track of people thinking the font size is too small
        /// </summary>
        /// <param name="feedItems"></param>
        private void ProcessSmallMessages(IEnumerable<FeedItem> feedItems)
        {
            // people has only a single vote, but they can change its value anytime
            feedItems.ToList().ForEach(item => { if (bigVoters.Contains(item.User)) bigVoters.Remove(item.User); });
            feedItems.ToList().ForEach(item => { if (!smallVoters.Contains(item.User)) smallVoters.Add(item.User); });

            // update text and animation based on new feedback received
            UpdateText();
            UpdateAnimation();
        }

        private void UpdateAnimation()
        {
            widgetAnimationService.Start(this);               
        }

        /// <summary>
        /// Keep track of people thinking the font size is too big
        /// </summary>
        /// <param name="feedItems"></param>
        private void ProcessBigMessages(IEnumerable<FeedItem> feedItems)
        {
            // people has only a single vote, but they can change its value anytime
            feedItems.ToList().ForEach(item => { if (smallVoters.Contains(item.User)) smallVoters.Remove(item.User); });
            feedItems.ToList().ForEach(item => { if (!bigVoters.Contains(item.User)) bigVoters.Add(item.User); });
            
            // update text and animation based on new feedback received
            UpdateText();
            UpdateAnimation();
        }

        private void UpdateText()
        {
            int voteTotal = bigVoters.Count + smallVoters.Count;
            double fontTooBigPerc = Convert.ToDouble(bigVoters.Count) / Convert.ToDouble(voteTotal);
            
            if (0.4 < fontTooBigPerc && fontTooBigPerc < 0.6)
            {
                // TODO: move to resource to ease localization
                fontSize.Advice = "perfect ;)";
            }
            else if (fontTooBigPerc <= 0.4)
            {
                // TODO: move to resource to ease localization
                fontSize.Advice = "too small!";
            }
            else 
            {
                // TODO: move to resource to ease localization
                fontSize.Advice = "too big!";
            }
        }

        /// <summary>
        /// Clicking on our widget means the speaker acknowledged the received feedback about the font size being too small or too big
        /// so we should stop the animation!
        /// </summary>
        /// <param name="source"></param>
        /// <param name="e"></param>
        private void fontSize_MouseDown(object source, MouseButtonEventArgs e)
        {
            widgetAnimationService.Stop(this);
        }

        /// <summary>
        /// Do not show any text when the audience is happy with the font size
        /// </summary>
        private void ResetAdvice()
        {
            // recreate lists
            bigVoters = new List<string>();
            smallVoters = new List<string>();
            // text should be blank
            fontSize.Advice = "";
        }

        /// <summary>
        /// Handle the increasing of the current font size
        /// </summary>
        /// <param name="source"></param>
        /// <param name="e"></param>
        private void fontSize_BigAClicked(object source, EventArgs e)
        {
            // if an animation is running we should stop it now
            widgetAnimationService.Stop(this);

            // set 50 as a safe limit on font size
            if (fontSize.Size < 50)
            {
                // increase the  font size
                fontSize.Size++;
                // set the new font size
                ReformatTextView(visualStudioService.GetActiveTextView(), fontSize.Size);
            }

            // changing the font size should reset the 'too small' or 'too big' legend
            ResetAdvice();
        }

        /// <summary>
        /// Handle the decreasing of the current font size
        /// </summary>
        /// <param name="source"></param>
        /// <param name="e"></param>
        private void fontSize_SmallAClicked(object source, EventArgs e)
        {
            widgetAnimationService.Stop(this);
            
            // dont go lower than one!
            if (fontSize.Size > 1)
            {
                // decrease font size by 1
                fontSize.Size--;
                // set the new font size
                ReformatTextView(visualStudioService.GetActiveTextView(), fontSize.Size);
            }

            // changing the font size should reset the 'too small' or 'too big' legend
            ResetAdvice();
        }

        /// <summary>
        /// Reset our widget data to its original state
        /// </summary>
        public void Reset()
        {
            fontSize.Size = GetCurrentTextSize(visualStudioService.GetActiveTextView());
            ResetAdvice();
        }

        public System.Windows.UIElement VisualElement
        {
            get
            {
                // this is our widget UI
                return fontSize;
            }
        }

        /// <summary>
        /// Retrieves the current font size of a given text view
        /// NOTE: The new managed editor doesn't provide just yet an easier way to do this
        /// that's why we needed to implement this retrieving VS services, etc.
        /// This may change in a future release of VS 2010
        /// </summary>
        /// <param name="textView"></param>
        /// <returns></returns>
        public double GetCurrentTextSize(IWpfTextView textView)
        {
            if (textView != null)
            {
                try
                {
                    Guid guidTextEditorFontCategory = new Guid("A27B4E24-A735-4d1d-B8E7-9716E1E3D8E0");
                    var storage = serviceProvider.GetService(typeof(SVsFontAndColorStorage)) as IVsFontAndColorStorage;
                    if (storage != null)
                    {
                        FontInfo[] fontInfo = new FontInfo[1];
                        fontInfo[0] = new FontInfo();

                        if (ErrorHandler.Succeeded(storage.OpenCategory(guidTextEditorFontCategory, (uint)__FCSTORAGEFLAGS.FCSF_LOADDEFAULTS)))
                        {
                            if (ErrorHandler.Succeeded(storage.GetFont(null, fontInfo)))
                            {
                                if (fontInfo != null && fontInfo.Count() > 0)
                                {
                                    return (double)fontInfo[0].wPointSize;
                                }
                            }
                            storage.CloseCategory();
                        }
                    }
                }
                catch
                {
                    // TODO: log the exception
                }
            }

            // if something fails just use a default size of 10
            return 10;
        }

        /// <summary>
        /// Set new font size
        /// NOTE: The new managed editor doesn't provide just yet an easier way to do this
        /// that's why we needed to implement this retrieving VS services, etc.
        /// This may change in a future release of VS 2010
        /// </summary>
        /// <param name="textView"></param>
        /// <param name="size"></param>
        public void ReformatTextView(IWpfTextView textView, double size)
        {
            try
            {
                Guid guidTextEditorFontCategory = new Guid("A27B4E24-A735-4d1d-B8E7-9716E1E3D8E0");

                var storage = serviceProvider.GetService(typeof(SVsFontAndColorStorage)) as IVsFontAndColorStorage;
                if (storage != null)
                {
                    FontInfo[] fontInfo = new FontInfo[1];
                    fontInfo[0] = new FontInfo();

                    if (ErrorHandler.Succeeded(storage.OpenCategory(guidTextEditorFontCategory, (uint)(__FCSTORAGEFLAGS.FCSF_LOADDEFAULTS | __FCSTORAGEFLAGS.FCSF_PROPAGATECHANGES))))
                    {
                        if (ErrorHandler.Succeeded(storage.GetFont(null, fontInfo)))
                        {
                            if (fontInfo != null && fontInfo.Count() > 0)
                            {
                                fontInfo[0].wPointSize = (ushort)size;
                                storage.SetFont(fontInfo);
                            }
                        }
                        storage.CloseCategory();
                    }
                }
            }
            catch
            {
                // TODO: log the exception
            }
        }
    }
}
